from __future__ import generators
"""
PosNET Client (Command line)
"""

import sys,getopt,os,sets,logging
from cStringIO import StringIO	
from pprint import pprint
import thread

import posnet_tcp_client
add_to_str = lambda o : "%s %s" % (o[0],o[1])
import random
import posnet_overlay_messages
import time
import cPickle

def usage():
	print >>sys.stderr,""
	print >>sys.stderr,"Usage:",os.path.split(sys.argv[0])[-1],"[options]","host","port" 
	print >>sys.stderr,""
	print >>sys.stderr,"Example:",os.path.split(sys.argv[0])[-1],"--ping","127.0.0.1","22000" 
	print >>sys.stderr,""
	print >>sys.stderr,"Options:"
	print >>sys.stderr,"\t -h | --help","print help message and exit"
	print >>sys.stderr,""
	print >>sys.stderr,"Network settings"
	print >>sys.stderr,"\t -a<value> | --answer=<value>","specify answer type. <value> =  0 (inside socket)} or 1 (with a returned message}"
	print >>sys.stderr,"\t --tunnel","*experimental* set to true to establish a tunnel to send messages"
	print >>sys.stderr,""
	print >>sys.stderr,"Control plane (overlay and semantic graph) "
	print >>sys.stderr,"\t --ping","get poset of a node - can be usefull to test if a node is alive"
	print >>sys.stderr,"\t --bettersplit","send better split to node"
	print >>sys.stderr,"\t --state","get state of semantic nodes"
	print >>sys.stderr,"\t --graph","get semantic graph (return a dictionnary printed to stdout with vertices and edges)"
	print >>sys.stderr,""
	print >>sys.stderr,"View posnet topology (overlay and semantic graph) "
	print >>sys.stderr,"\t --draw=<output_directory>","draw overlay and semantic graph topology. Write files to <output_directory> "
	print >>sys.stderr,"\t --points=<output_directory>","Write points file and links file to <output_directory> "
	print >>sys.stderr,"\t --export=<output_directory>","Write points file and links file to <output_directory> "
	print >>sys.stderr,""
	print >>sys.stderr,"Save results of Queries" 
	print >>sys.stderr,"\t --save=<output_directory>","Write query result to <output_directory>/<output_file_automatic_name> "
	print >>sys.stderr,"\t --append=<output_file>","Append query result to <output_file> "
	print >>sys.stderr,""
	print >>sys.stderr,"Add data to the overlay"
	print >>sys.stderr,"\t --handlesize=N","set size of handles!"
	print >>sys.stderr,"\t --add=v1,...,vk","add the point (v1,...,vk) to overlay"
	print >>sys.stderr,"\t --addrandom","add a random point to overlay"
	print >>sys.stderr,"\t --boundmin=N","set min of component values (for random generation)"
	print >>sys.stderr,"\t --boundmax=N","set max of component values (for random generation)"
	print >>sys.stderr,"\t --bulkaddrandom=N","add N random points to overlay"
	print >>sys.stderr,"\t --load=N","load points from file (be careful the format should correspond to poset stored in overlay node conctated)"
	print >>sys.stderr,"\t --init=<linksfile>","load entire semantic_graph from <linksfile>.\\n \\tBe careful the node should be empty to receive the semantic_graph provided"
	print >>sys.stderr,""
	print >>sys.stderr,"Query the distributed semantic graph"
	print >>sys.stderr,"\t no argument queries"
	print >>sys.stderr,"\t --all_top","query all top semantic nodes of the overlay"
	print >>sys.stderr,"\t point queries"
	print >>sys.stderr,"\t --exact=v1,...,vk","query to request for existence of a semantic point"
	print >>sys.stderr,"\t --parents=v1,...,vk","query to get parent nodes of a semantic point"
	print >>sys.stderr,"\t --all_children=v1,...,vk","query to get all children nodes of a semantic point"
	print >>sys.stderr,"\t --all_parents=v1,...,vk","query to get all parent nodes of a semantic point"
	print >>sys.stderr,"\t --all_contained=v1,...,vk","query to get all nodes contained"
	print >>sys.stderr,"\t --all_containers=v1,...,vk","query to get all nodes contained"
	print >>sys.stderr,"\t range queries"
	print >>sys.stderr,"\t --all_match=l1,...,lk,u1,...,uk","range query: all semantic nodes of the overlay matching l1,...lk<  <u1,...,uk"
	print >>sys.stderr,""
	print >>sys.stderr,"Query the posnet overlay"
	print >>sys.stderr,"\t --overlay","get all overlay nodes"
		

def posnet_client_algorithms_zero_args():
	point_queries = sets.Set()
	point_queries.add("all_top")
	return point_queries
	
def posnet_client_algorithms_one_args():
	point_queries = sets.Set()
	point_queries.add("all_containers")
	point_queries.add("all_contained")
	point_queries.add("all_children")
	point_queries.add("all_parents")
	point_queries.add("parents")
	point_queries.add("exact")
	return point_queries

def posnet_client_algorithms_two_args():
	range_queries = sets.Set()
	range_queries.add("all_match")
	return range_queries

# if tunnel exist, then the two global variables will contain tunnel information
tunnel = None
tunnel_local_address = None


def posnet_client_main(logger = None):
	point_queries_zero = posnet_client_algorithms_zero_args()
	point_queries = posnet_client_algorithms_one_args()
	range_queries= posnet_client_algorithms_two_args() 
	try:
		opts_long = ["help","answer=","tunnel","ping","bettersplit","state","graph","add=","addrandom","bulkaddrandom=","draw=","points=","export=","handlesize=","boundmin=","boudmax=","load=","init=","overlay","save=","append="]
		for point_query in point_queries_zero:
			opts_long.append(point_query)
		for point_query in point_queries:
			opts_long.append(point_query+"=")
		for range_query in range_queries:
			opts_long.append(range_query+"=")
		opts_short = "ha:v"
		opts, _args = getopt.getopt(sys.argv[1:],opts_short,opts_long)
		args  = []
		opts_after_args = [] 
		for x in _args:
			if x.startswith("--"):  opts_after_args.append( x )
			else: args.append( x )
		_opts, _args = getopt.getopt(opts_after_args, opts_short,opts_long)
		opts.extend( _opts )
	except getopt.GetoptError:
		# print help information and exit:
		usage()
		sys.exit(2)
	if len(args) < 2:
		usage()
		sys.exit(2)

	host = args[0] # server address
	try:
		port = int(args[1]) # server port
	except ValueError:
		usage()
		sys.exit(2)
	#-------------------
	output = "/tmp/"
	answer_type = 0	
	boundmin = 1
	boundmax = 10000
	handle_size = 0	
	commands = sets.Set()
	tunnel_opt = False
	points_file = None
	linksfile = None
	savequery = None	
	appendquery = None	
	#-------------------
	for o, value in opts:
		if o in ("-h", "--help"):
			usage()
			sys.exit()
		elif o in ("-a", "--answer"):
			answer_type = value
		elif o in ("--handlesize"):
			handle_size = int(value)
		elif o in ("--boundmin"):
			boundmin = int(value)
		elif o in ("--boundmax"):
			boundmax = int(value)
		elif o in ("--tunnel"):
			tunnel_opt = True
		elif o in ("--load"):
			points_file = value
			if not os.path.exists(points_file):
				points_file = None
				raise Exception("points file %s does not exist"%str(value))
		elif o in ("--init"):
			linksfile = value
			if not os.path.exists(linksfile):
				linksfile = None
				raise Exception("linksfile %s does not exist"%str(value))
		elif o in ("--save"):
			savequery = value
			if not os.path.exists(savequery):
				os.makedirs(savequery)
		elif o in ("--append"):
			appendquery = value
			if os.path.exists(appendquery):
				if os.path.isdir(appendquery): raise Exception(" %s is a directory and it should be a file to append data "%str(appendquery))
		#-------------------------	
		#-------------------------	
		elif o.startswith("--"):
			commands.add( ( o[2:], value)   )
	cmd_to_pprint = { "state" : posnet_tcp_client.posnet_state_request, \
			 # "ping" : posnet_tcp_client.posnet_ping, \
			  "graph" : posnet_tcp_client.posnet_graph_request, \
			"": ""	
			}
	#--------------------
	# tunnel establishment
	global tunnel
	global tunnel_local_address
	tunnel_thread =  None
	if tunnel_opt:
		import threading,socket
		tunnel_socket_binding = posnet_tcp_client.allocate_tunnel()
		tunnel_contact_host = socket.gethostbyname(socket.gethostname()) #tunnel_socket_binding.getsockname()[0]
		tunnel_contact_port = tunnel_socket_binding.getsockname()[1]
		tunnel_create_address = (tunnel_contact_host,tunnel_contact_port)
		tunnel_wait = threading.Event()
		tunnel_established = threading.Event()
		tunnel_established.clear()
		def wait_for_tunnel_establishment(tunnel_socket_binding):
			global tunnel
			global tunnel_local_address
			try:
				tunnel_wait.set()
				tunnel_socket = posnet_tcp_client.tunnel_wait_for_establishment(tunnel_socket_binding)
				
				if tunnel_socket is None: return
				tunnel_local_address_ = tunnel_socket.getsockname()
				# fill up
				tunnel = tunnel_socket
				tunnel_local_address = tunnel_local_address_
				tunnel_established.set()
				#-----------
				if logger is not None: logger.info("tunnel established, local address %s"%str(tunnel_local_address))
				while 1:
					msg = posnet_tcp_client.tunnel_recv_message(tunnel_socket)
					logger.info("message received over tunnel %s"%str(msg))
					print msg				
			except Exception,e:
				tunnel_wait.set()
				tunnel_established.set()
				from traceback import print_exc
				print "Tunnel contact: estblishment error to %s: %s"%(str(tunnel_socket_binding),str(e))
				print_exc() 
		tunnel_thread = thread.start_new_thread( wait_for_tunnel_establishment, (tunnel_socket_binding,) )
		tunnel_wait.wait()
		tunnel_result = posnet_tcp_client.posnet_tunnel((host,port),tunnel_create_address,0)
		#------------------------------
		tunnel_established.wait()
		#test tunnel
		if tunnel is not None and False:
			print "tunnel local address",tunnel_local_address
			print "tunnel",tunnel
			m = posnet_overlay_messages.posnet_ping_message(  tunnel_local_address, (host,port), "")
			posnet_tcp_client.tunnel_send_message( tunnel,m )
			time.sleep(3)
			print "okkkkk"
			sys.stdout.flush()
			time.sleep(3) 
	#---------------------------
	# before executing commands do what can be done
	#
	
	default_handle = StringIO()
	for nn in xrange(handle_size): default_handle.write('1')
	if boundmax < boundmin:
		raise Exception("please respect boundmin<  < boundmax") 
	if linksfile is not None:
		to_init = dict()
		to_init["graph"] = posnet_tcp_client.posnet_parse_links(linksfile)
		init_message = posnet_overlay_messages.posnet_initialize_message(("",0),(host,port),to_init)
		if tunnel is not None:
			posnet_tcp_client.tunnel_send_message(tunnel,init_message)
		else:
			posnet_tcp_client.send_message((host,port),init_message,0)
	# --load option -> points_file
	if points_file is not None:
		points_to_add = sets.Set()
		points_data_descriptor = file(points_file,"r")
		line = points_data_descriptor.readline()
		while len(line) > 0:
			try:
				point_to_add = [int(y) for y in line[:-1].split("\t")]
				points_to_add.add(tuple(point_to_add))
			except ValueError:
				if logger is not None:
					logger.error("line not correct %s"%str(line[:-1]))
			line = points_data_descriptor.readline()
		if logger is not None:
			logger.info("insert %s points from file %s"%(len(points_to_add),points_file))
		for point in points_to_add:
			#MIKK
			if tunnel is not None:
				add_handle_message = posnet_overlay_messages.posnet_add_handle(point,default_handle.getvalue(),True)
				message = posnet_overlay_messages.posnet_overlay_add_handle_message( tunnel_local_address,(host,port),   add_handle_message)
				posnet_tcp_client.tunnel_send_message(tunnel,message)
			else: posnet_tcp_client.posnet_add_handle( (host,port),  point, default_handle.getvalue())
	
	#---------------------------	
	for cmd,arg in commands:
		if logger is None: print "execute command %s to overlay node %s "% (cmd ,(host,port),)
		else: logger.debug("execute command %s to overlay node %s "% (cmd ,(host,port),))
		try:
			func = cmd_to_pprint[cmd]	
			Data  = func( (host,port), answer_type )
			pprint(Data)
		except KeyError:
			if cmd == "ping":
				thePoset = posnet_tcp_client.posnet_ping((host,port), 0) #answer_type )
				keys = thePoset.keys()
				keys.sort()
				print "Poset has %d keys"%(len(keys))
				for k in keys:
					print "key %d: %s"%(k[0],k[1])
			elif cmd == "bettersplit":
				posnet_tcp_client.posnet_better_split((host,port), 0)
			elif cmd == "draw":
				posnet_tcp_client.posnet_draw_topology((host,port),arg)
			elif cmd == "overlay":
				overlay_nodes = posnet_tcp_client.posnet_overlay_topology((host,port))
				for node in overlay_nodes:
					address,port = node
					print "overlay_node %s %s"%(address,port)
			
			elif cmd == "points" or cmd == "export":
				posnet_tcp_client.posnet_export_topology((host,port),arg)
			elif cmd == "add":
				point = [int(x) for x in arg.split(",")]
				posnet_tcp_client.posnet_add_handle( (host,port),  point, default_handle.getvalue())
			elif cmd == "addrandom" or cmd == "bulkaddrandom":
				Poset = posnet_tcp_client.posnet_ping( (host,port))
				Poset_keys = Poset.keys()
				print len(Poset_keys)
				number = 1
				if cmd == "bulkaddrandom": number = int( arg )	
				for iter in xrange(number):
					
					if tunnel is not None:
						# get random
						point = [random.randrange(boundmin,boundmax) for y in Poset_keys]
						add_handle_message = posnet_overlay_messages.posnet_add_handle(point,default_handle.getvalue(),True)
						message = posnet_overlay_messages.posnet_overlay_add_handle_message( tunnel_local_address,(host,port),   add_handle_message)
						posnet_tcp_client.tunnel_send_message(tunnel,message)
					else:
						posnet_tcp_client.posnet_add_random_handle_range( (host,port), Poset_keys, boundmin, boundmax,default_handle.getvalue(),logger)
			elif cmd == "all_top":
				results = dict()
				for ack_msg in  posnet_tcp_client.posnet_point_query( (host,port) ,"all_top (Point query)",None ):
					try: results[ack_msg.source].extend( ack_msg.data.results )
					except KeyError:  results[ack_msg.source] =  ack_msg.data.results 
				for result_source,the_results in results.items():
					for result in the_results:
						if logger is None: print "%s %s -> %s"% (cmd,add_to_str(result_source),result)
						else: logger.info("%s %s -> %s"% (cmd,add_to_str(result_source),result))
			elif cmd in point_queries:	
				point = [int(x) for x in arg.split(",")]
				query_data = posnet_tcp_client.posnet_point_query2( (host,port) ,cmd,point)
				if savequery is not None:
					pkl_f = file(os.path.join(savequery,cmd+"."+str(time.time()).split(".")[0]+"."+query_data["id"]+".pkl"),"wb")
					cPickle.dump(query_data,pkl_f)
					pkl_f.close()
				elif appendquery is not None:
					pkl_f = file(appendquery,"ab")
					cPickle.dump(query_data,pkl_f)
					pkl_f.close()
			elif cmd in range_queries:	
				two_points = [int(x) for x in arg.split(",")]
				range_inf = tuple(two_points[:len(two_points)/2]) 
				range_sup = tuple(two_points[len(two_points)/2:]) 
				query_data = posnet_tcp_client.posnet_range_query2( (host,port) ,cmd,range_inf,range_sup)
				if savequery is not None:
					pkl_f = file(os.path.join(savequery,cmd+"."+str(time.time()).split(".")[0]+"."+query_data["id"]+".pkl"),"wb")
					cPickle.dump(query_data,pkl_f)
					pkl_f.close()
				elif appendquery is not None:
					pkl_f = file(appendquery,"ab")
					cPickle.dump(query_data,pkl_f)
					pkl_f.close()

if __name__ == "__main__":
	#-----------------
	# Logging 
	# set up logging to file
	logging.basicConfig(level=logging.INFO, #DEBUG,
			    #format='%(asctime)s %(name)-12s %(levelname)-8s %(message)s',
			    format='%(created)s %(name)-12s %(levelname)-8s %(message)s',
			    datefmt='%m-%d %H:%M:s'  #,
			    #filename='posnet_overlay_node2_'+myHost+'_'+str(listen_port)+'.log',
			    #filemode='w'
				)
	# define a Handler which writes INFO messages or higher to the sys.stderr
	console = logging.StreamHandler()
	console.setLevel(logging.DEBUG) #.INFO)
	formatter = logging.Formatter('%(name)-12s: %(levelname)-3s %(message)s') # set a format which is simpler for console use
	console.setFormatter(formatter) # tell the handler to use this format
	#logging.getLogger('').addHandler(console) # add the handler to the root logger
	#---	
	logger = logging.getLogger("posnet.client")
	posnet_client_main(logger)
